package com.ruoyi.web.core.netty;

import cn.hutool.core.bean.BeanUtil;
import com.github.misterchangray.core.MagicByte;
import com.ruoyi.RuoYiApplication;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.lh.domain.Machine;
import com.ruoyi.lh.domain.Software;
import com.ruoyi.lh.service.IMachineService;
import com.ruoyi.lh.service.ISoftwareService;
import com.ruoyi.lh.service.IUserService;
import com.ruoyi.web.controller.dto.CheckUpdateParams;
import com.ruoyi.web.controller.dto.CheckUpdateResponse;
import com.ruoyi.web.controller.dto.LoginBackDto;
import com.ruoyi.web.controller.dto.LoginParams;
import com.ruoyi.web.core.send.LoginSendMagicDto;
import com.ruoyi.web.core.tool.ByteUtils;
import com.ruoyi.web.core.tool.CRC16M;
import com.ruoyi.web.core.receive.*;
import com.ruoyi.web.core.send.CheckUpdateSendMagicDTO;
import com.ruoyi.web.core.send.DownloadFileSendMagicDto;
import com.ruoyi.web.core.send.DownloadSendMagicDto;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import java.io.*;
import java.math.BigInteger;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

  public static final Logger LOGGER = LoggerFactory.getLogger(NettyServerHandler.class);

  //不为空值
  //Context是实现ApplicationContextAware接口获取ApplicationContext上下文对象，通过getBean的方式，获取到Spring容器的Bean
  IUserService userService = RuoYiApplication.ac.getBean(IUserService.class);

  ISoftwareService softwareService = RuoYiApplication.ac.getBean(ISoftwareService.class);

  IMachineService machineService = RuoYiApplication.ac.getBean(IMachineService.class);


  /**
   * 有客户端与服务器发生连接时执行此方法
   * 1.打印提示信息
   * 2.将客户端保存到 channelGroup 中
   */
  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    Channel channel = ctx.channel();
    System.err.println("有新的客户端与服务器发生连接。客户端地址：" + channel.remoteAddress());
  }

  /**
   * 当有客户端与服务器断开连接时执行此方法，此时会自动将此客户端从 channelGroup 中移除
   * 1.打印提示信息
   */
  @Override
  public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    Channel channel = ctx.channel();
    System.err.println("有客户端与服务器断开|连接。客户端地址：" + channel.remoteAddress());
  }

  @Override
  public void channelRead(ChannelHandlerContext chc, Object o) throws Exception {
//    System.out.println("Client->Server:" + chc.channel().remoteAddress() + " send " + o.toString());
    //包头	包序号	目标地址	源地址	CRC16	主ID	子ID	数据类型	数据长度	数据
    //4Byte	1Byte	2Byte	2Byte	2Byte	1Byte	1Byte	1Byte	2Byte	NByte

    String message = o.toString();
    // 消息头
    String header = message.substring(0, 18);//包头+包序号+目标地址+源地址
    // 取消息正文
    String body = message.substring(32);//数据
    String id = message.substring(22, 26);//主ID+子ID
    String crc16 = message.substring(18,22);//CRC16
    String modbus  = message.substring(22);//CRC16，数据校验，校验范围：主ID+子ID+数据类型+数据长度+数据。采用CRC-16/MODBUS X16+X15+X2+1。
    //crc16校验
    String CRC16 = ByteUtils.byteToString(CRC16M.get(HexDecoder.hexStringToBytes(modbus)));
    if(!crc16.equals(CRC16)){
      LOGGER.error("Client->Server:{}; 【CRC16校验异常】",chc.channel().remoteAddress());
      return;
    }
    handler(id, header, body, chc);
  }

  public void handler(String id, String header, String message, ChannelHandlerContext chc) throws Exception {
    byte[] unpack = null;

    if (id.equals(IdEnum.LOGIN.getValue())) {
      LoginReceiveMagicDto loginReceiveMagicDto = MagicByte.pack(HexDecoder.hexStringToBytes(message), LoginReceiveMagicDto.class);
      LoginParams loginParams = BeanUtil.copyProperties(loginReceiveMagicDto, LoginParams.class);
      AjaxResult login = userService.login(loginParams);
      if (login.isSuccess()) {
        LoginBackDto data = (LoginBackDto) login.get("data");
        LoginSendMagicDto loginSendMagicDto = BeanUtil.copyProperties(data, LoginSendMagicDto.class);
        int s = 1800;//30分钟，就是系统的会话时间
        loginSendMagicDto.setM_ulHoldTime(s);
        loginSendMagicDto.setM_ulLogoutTime(s);
        unpack = MagicByte.unpackToByte(loginSendMagicDto);
      }
    }else if (id.equals(IdEnum.LOGOUT.getValue())) {
      LogoutReceiveMagicDto logoutReceiveMagicDto = MagicByte.pack(HexDecoder.hexStringToBytes(message), LogoutReceiveMagicDto.class);
      AjaxResult logout = userService.logout(logoutReceiveMagicDto.getM_ucConnectId());
    }else if (id.equals(IdEnum.CHECK_UPDATE.getValue())) {// 请求固件信息
      CheckUpdateReceiveMagicDTO checkUpdateReceiveMagicDTO = MagicByte.pack(HexDecoder.hexStringToBytes(message), CheckUpdateReceiveMagicDTO.class);
      CheckUpdateParams checkUpdateParams = BeanUtil.copyProperties(checkUpdateReceiveMagicDTO, CheckUpdateParams.class);
      Machine machine = new Machine();
      machine.setSn(checkUpdateParams.getM_uiSoftDevSN());
      List<Machine> machines = machineService.selectMachineList(machine);

      if(machines!=null && machines.size()>0 && machines.get(0).getReleaseArea()!=null){
        checkUpdateParams.setM_uiSoftArea(machines.get(0).getReleaseArea());
      }
      AjaxResult ajaxResult = userService.checkUpdate(checkUpdateParams);
      //如果是禁止更新，则直接返回无更新
      if(machines != null && machines.size() > 0  && ("1".equals(machines.get(0).getIsAcceptUpdate())
        || "1".equals(machines.get(0).getStatus()) || checkUpdateReceiveMagicDTO.getM_ucCurSV() == null || "".equals(checkUpdateReceiveMagicDTO.getM_ucCurSV())
      )){
        ajaxResult = new AjaxResult(
                AjaxResult.Type.SUCCESS,
                "当前无需更新",
                new CheckUpdateResponse("0", null, null, null, null, null, null, null, null));
      }
      CheckUpdateResponse data = (CheckUpdateResponse) ajaxResult.get("data");
      for (FileTypeEnum filetype : FileTypeEnum.values()) {
        if(filetype.getDesc().equalsIgnoreCase(data.getM_uiFileType())){
          data.setM_uiFileType(filetype.getValue());
          break;
        }
      }
      // 复制data，并忽略m_uiMD5，m_ucFileSize
      CheckUpdateSendMagicDTO checkUpdateSendMagicDTO = BeanUtil.copyProperties(data, CheckUpdateSendMagicDTO.class, "m_uiMD5", "m_ucFileSize");
      if(data.getM_uiUpdateDesc() != null){
        //因为文件描述是不固定长度，所以就手动转换
        byte[] getBytesFromString = data.getM_uiUpdateDesc().getBytes(StandardCharsets.UTF_8);
        BigInteger bigInteger = new BigInteger( 1, getBytesFromString);
        String convertedResult = String.format("%x",bigInteger);
        byte[] UpdateDescByte = HexDecoder.hexStringToBytes(convertedResult);
        byte[] oldUnpack = MagicByte.unpackToByte(checkUpdateSendMagicDTO);
        byte[] newUnpack = new byte[oldUnpack.length + UpdateDescByte.length];
        //将oldUnpack的0位置开始复制到newUnpack中从0下标开始复制oldUnpack.length 长度
        System.arraycopy(oldUnpack, 0, newUnpack, 0, oldUnpack.length);
        System.arraycopy(UpdateDescByte, 0, newUnpack, oldUnpack.length, UpdateDescByte.length);
        unpack = newUnpack;
      }else{
        unpack = MagicByte.unpackToByte(checkUpdateSendMagicDTO);
      }
    }else if (id.equals(IdEnum.DOWNLOAD_FILE.getValue())) {
      DownloadFileReceiveMagicDto downloadFileReceiveMagicDto = MagicByte.pack(HexDecoder.hexStringToBytes(message), DownloadFileReceiveMagicDto.class);
      if (downloadFileReceiveMagicDto.getM_uiPackageSize() == 0) {
        //解析出错, 进行手动解析
        String packSizeLittle = message.substring(message.length() - 4);
        int packSizeBig = toBigInt(packSizeLittle);
        downloadFileReceiveMagicDto.setM_uiPackageSize(packSizeBig);
      }
      Software software = softwareService.selectSoftwareById(Long.valueOf(downloadFileReceiveMagicDto.getM_ucFileNO()));
      if (software != null) {
        // 本地资源路径
        String localPath = RuoYiConfig.getProfile();
        // 数据库资源地址
        String filePath = localPath + StringUtils.substringAfter(software.getUpdateFile(), Constants.RESOURCE_PREFIX);
        File file = new File(filePath);
        DownloadFileSendMagicDto downloadFileSendMagicDto = BeanUtil.copyProperties(downloadFileReceiveMagicDto, DownloadFileSendMagicDto.class);
        downloadFileSendMagicDto.setM_uiPackageNum((short) Math.ceil(file.length() * 1.0 / downloadFileReceiveMagicDto.getM_uiPackageSize()));
        downloadFileSendMagicDto.setM_uiFileSize((int)file.length());
        byte[] readFile = readFile(file);
        byte[] CRC16 = CRC16M.get(readFile);
        // TODO: 2023-10-01  暂时设定固定值 方便校验是否正确
//        downloadFileSendMagicDto.setM_uiPackageNum((short)10);
//        downloadFileSendMagicDto.setM_uiFileSize(1000);
        byte[] oldUnpack = MagicByte.unpackToByte(downloadFileSendMagicDto);
        byte[] newUnpack = new byte[oldUnpack.length + CRC16.length];
        System.arraycopy(oldUnpack, 0, newUnpack, 0, oldUnpack.length);
        System.arraycopy(CRC16, 0, newUnpack, oldUnpack.length, CRC16.length);
        unpack = newUnpack;
      }
    }else if (id.equals(IdEnum.DOWNLOAD.getValue())) { //下载固件数据
      DownloadReceiveMagicDto downloadReceiveMagicDto = MagicByte.pack(HexDecoder.hexStringToBytes(message), DownloadReceiveMagicDto.class);
      if (downloadReceiveMagicDto.getM_uiPackageSize() == 0) {
        //解析出错, 进行手动解析
        String packSizeLittle = message.substring(message.length() - 4);
        int packSizeBig = toBigInt(packSizeLittle);
        downloadReceiveMagicDto.setM_uiPackageSize(packSizeBig);
      }
      Software software = softwareService.selectSoftwareById(Long.valueOf(downloadReceiveMagicDto.getM_ucFileNO()));
      if (software != null) {
        // 本地资源路径
        String localPath = RuoYiConfig.getProfile();
        // 数据库资源地址
        String filePath = localPath + StringUtils.substringAfter(software.getUpdateFile(), Constants.RESOURCE_PREFIX);
        File file = new File(filePath);
        String fileName = file.getName().substring(0, file.getName().lastIndexOf("."));
        String suffix  = file.getName().substring(file.getName().lastIndexOf(".") + 1);//文件后缀名
        DownloadSendMagicDto downloadSendMagicDto = BeanUtil.copyProperties(downloadReceiveMagicDto, DownloadSendMagicDto.class);
//        downloadSendMagicDto.setM_uiPackgeLen((short) (file.length() / downloadReceiveMagicDto.getM_uiPackageSize()));
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
        FileChannel channel = randomAccessFile.getChannel();
        long size = channel.size();//文件总大小
        int packageNum = downloadSendMagicDto.getM_uiPackageNum();//拆包总数
        int m_uiPackageSize = downloadReceiveMagicDto.getM_uiPackageSize();//拆包大小
        byte[] fileBytes = null;
        for (int i = 0; i < packageNum; i++) {
          //读取指定下标的拆包后的文件
          if(i == downloadReceiveMagicDto.getM_uiPackageIndex()){
            int startIndex = i * m_uiPackageSize;//读取开始位置
            int endIndex = startIndex + m_uiPackageSize;//结束位置
            //当前读取的大小
            int count = m_uiPackageSize;
            //判断剩下的大小是否足够，不够则取剩下的长度
            if(endIndex + 1 > size){
              count = Math.toIntExact(size - (long) startIndex);
              downloadSendMagicDto.setM_uiPackageSize((short) count);
            }
            fileBytes = getFiles(randomAccessFile, startIndex, count);
            FileOutputStream out = new FileOutputStream(fileName + "(" + (i + 1) + ")");
            out.write(fileBytes);
            out.flush();
            out.close();
            break;
          }
        }
        if(fileBytes!=null){
//          BigInteger bigInteger = new BigInteger( 1, fileBytes);
//          String convertedResult = String.format("%x",bigInteger);
//          byte[] fileByte = HexDecoder.hexStringToBytes(convertedResult);
          byte[] oldUnpack = MagicByte.unpackToByte(downloadSendMagicDto);
          byte[] newUnpack = new byte[oldUnpack.length + fileBytes.length];
          //将oldUnpack的0位置开始复制到newUnpack中从0下标开始复制oldUnpack.length 长度
          System.arraycopy(oldUnpack, 0, newUnpack, 0, oldUnpack.length);
          System.arraycopy(fileBytes, 0, newUnpack, oldUnpack.length, fileBytes.length);
          unpack = newUnpack;
        }else{
          unpack = MagicByte.unpackToByte(downloadSendMagicDto);
        }
      }
    }
    if (unpack != null) {
      send2(header, id, unpack, chc);
//      send(header, id, unpack, chc);
    }
  }

  /**
   * 拆分文件
   * @param file 文件
   * @param seek 开始的位置
   * @param count 读取的长度
   * @return
   * @throws Exception
   */
  public static byte[] getFiles(RandomAccessFile file, long seek, int count) throws Exception {
    file.seek(seek);
    byte[] bytes = new byte[count];
    file.read(bytes);
    return bytes;
  }

  /**
   * 发送应答数据
   * @param header
   * @param id
   * @param unpack
   * @param chc
   */
  private void send(String header,String id,byte[] unpack,ChannelHandlerContext chc){
    String body = HexDecoder.bytesToHexString(unpack);//数据
    System.out.println(unpack.length);
    LOGGER.info("数据体：{}",body);

    //CRC16组成的16进制字符串
    String beforeCrc16 = id + "04" + switchHexLittle(body.length()) + body;
    // crc16校验
    String CRC16 = ByteUtils.byteToString(CRC16M.get(HexDecoder.hexStringToBytes(beforeCrc16)));

    String returnData = header + CRC16 + beforeCrc16;
    byte[] hexBytes = HexDecoder.hexStringToBytes(returnData);
    LOGGER.info("返回应答16进制字节数组：{}",hexBytes);
    LOGGER.info("返回应答16进制字符串：{}",returnData);
    chc.writeAndFlush(hexBytes);
  }

  private void send2(String header,String id,byte[] unpack,ChannelHandlerContext chc){
    // header
    byte[] _header = HexDecoder.hexStringToBytes(header);

    // id
    byte[] _id = HexDecoder.hexStringToBytes(id);
//    System.out.println(Arrays.toString(_id));

    // 数据类型：应答指令
    byte _command = 4;

    // 数据长度
    byte[] _length = HexDecoder.hexStringToBytes(switchHexLittle(unpack.length));
//    System.out.println(Arrays.toString(_length));

    // CRC16
    byte[] _before_crr16 = new byte[5 + unpack.length];
    System.arraycopy(_id, 0, _before_crr16, 0, _id.length);
    _before_crr16[2] = _command;
    System.arraycopy(_length, 0, _before_crr16, 3, _length.length);
    System.arraycopy(unpack, 0, _before_crr16, 5, unpack.length);
//    String _before_crr16_str = HexDecoder.bytesToHexString(_before_crr16);
//    System.out.println(_before_crr16_str);

    byte[] _crc16 = CRC16M.get(_before_crr16);
//    System.out.println(Arrays.toString(_crc16));
//    String _crc16Str = HexDecoder.bytesToHexString(_crc16);
//    System.out.println(_crc16Str);

    byte[] _send_data = new byte[_header.length + _crc16.length + _before_crr16.length];
    System.arraycopy(_header, 0, _send_data, 0, _header.length);
    System.arraycopy(_crc16, 0, _send_data, _header.length, _crc16.length);
    System.arraycopy(_before_crr16, 0, _send_data, _header.length + _crc16.length, _before_crr16.length);
    String _send_data_str = "";
    _send_data_str = HexDecoder.bytesToHexString(_send_data);
//    System.out.println(_send_data_str);
    LOGGER.info("返回应答16进制字节数组：{}",_send_data);
    LOGGER.info("返回应答16进制字符串：{}",_send_data_str);
    chc.writeAndFlush(_send_data);
  }

  private byte[] readFile(File file) throws IOException {
    FileInputStream fis = new FileInputStream(file);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length;
    while ((length = fis.read(buffer)) != -1) {
      bos.write(buffer, 0, length);
    }
    byte[] byteData = bos.toByteArray();
    return byteData;
  }

  public static void main(String[] args) {
    long a = 10;
    int b = 10000;
    double ceil = Math.ceil(a * 1.0/b);
    System.out.println(ceil + ";" + (short) ceil);
    int i = Math.toIntExact(a - (long) (b + 1));
    System.out.println(i);

    short X = (short) 60000;

    int s = 1800;
    String id = IdEnum.LOGIN.getValue();
    String header = "7E7E7E7E010100FFFF";

    LoginSendMagicDto loginSendMagicDto = new LoginSendMagicDto();
    loginSendMagicDto.setM_ulHoldTime(s);
    loginSendMagicDto.setM_ulLogoutTime(s);
    loginSendMagicDto.setM_ucConnectId("abcdefhhi1qwer4a");

    byte[] unpack = MagicByte.unpackToByte(loginSendMagicDto);
    System.out.println(Arrays.toString(unpack));

    // header
    byte[] _header = HexDecoder.hexStringToBytes(header);

    // id
    byte[] _id = HexDecoder.hexStringToBytes(id);
    System.out.println(Arrays.toString(_id));

    // 数据类型：应答指令
    byte _command = 4;

    // 数据长度
    byte[] _length = HexDecoder.hexStringToBytes(switchHexLittle(unpack.length));
    System.out.println(Arrays.toString(_length));

    // CRC16
    byte[] _before_crr16 = new byte[5 + unpack.length];
    System.arraycopy(_id, 0, _before_crr16, 0, _id.length);
    _before_crr16[2] = _command;
    System.arraycopy(_length, 0, _before_crr16, 3, _length.length);
    System.arraycopy(unpack, 0, _before_crr16, 5, unpack.length);
    String _before_crr16_str = HexDecoder.bytesToHexString(_before_crr16);
    System.out.println(_before_crr16_str);

    byte[] _crc16 = CRC16M.get(_before_crr16);
    System.out.println(Arrays.toString(_crc16));
    String _crc16Str = HexDecoder.bytesToHexString(_crc16);
    System.out.println(_crc16Str);

    byte[] _send_data = new byte[_header.length + _crc16.length + _before_crr16.length];
    System.arraycopy(_header, 0, _send_data, 0, _header.length);
    System.arraycopy(_crc16, 0, _send_data, _header.length, _crc16.length);
    System.arraycopy(_before_crr16, 0, _send_data, _header.length + _crc16.length, _before_crr16.length);
    String _send_data_str = "";
    _send_data_str = HexDecoder.bytesToHexString(_send_data);
    System.out.println(_send_data_str);
  }

  private int toBigInt(String s) {
    String s1 = s.substring(0,2);
    String s2 = s.substring(2,4);
    s = s2 + s1;
    return Integer.parseInt(s, 16);
  }

  /**
   * int转16进制小端
   * @param i
   * @return
   */
  private static String switchHexLittle(int i) {
    String tmp = Integer.toHexString(i);
    if (tmp.length() == 1 || tmp.length() == 3) {
      tmp = "0" + tmp;
    }
    if (tmp.length() == 4) {
      String s1 = tmp.substring(0,2);
      String s2 = tmp.substring(2,4);
      tmp = s2 + s1;
    }else if (tmp.length() == 2) {
      tmp = tmp + "00";
    }
    return tmp;
  }

  private String switchHexLittleLong(long l) {
    String tmp = Long.toHexString(l);
    if (tmp.length() == 1 || tmp.length() == 3 || tmp.length() == 5 || tmp.length() == 7) {
      tmp = "0" + tmp;
    }
    if (tmp.length() == 8) {
      String s1 = tmp.substring(0,2);
      String s2 = tmp.substring(2,4);
      String s3 = tmp.substring(4,6);
      String s4 = tmp.substring(6,8);
      tmp = s4 + s3 + s2 + s1;
    }
    else if (tmp.length() == 6) {
      String s1 = tmp.substring(0,2);
      String s2 = tmp.substring(2,4);
      String s3 = tmp.substring(4,6);
      tmp = s3 + s2 + s1 + "00";
    }
    else if (tmp.length() == 4) {
      String s1 = tmp.substring(0,2);
      String s2 = tmp.substring(2,4);
      tmp = s2 + s1 + "0000";
    }else if (tmp.length() == 2) {
      tmp = tmp + "000000";
    }
    return tmp;
  }
}
